package jnpf.database.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationAdvisor;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.ds.ItemDataSource;
import com.baomidou.dynamic.datasource.processor.DsProcessor;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.incrementer.H2KeyGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.github.pagehelper.PageInterceptor;
import jnpf.config.ConfigValueUtil;
import jnpf.database.model.dbtable.DbTableFieldModel;
import jnpf.database.model.dto.PrepareSqlDTO;
import jnpf.database.plugins.DynamicGeneratorInterceptor;
import jnpf.database.plugins.MyMasterSlaveAutoRoutingPlugin;
import jnpf.database.source.DbBase;
import jnpf.database.source.impl.DbOracle;
import jnpf.database.sql.enums.base.SqlComEnum;
import jnpf.database.sql.util.SqlFastUtil;
import jnpf.database.util.ConnUtil;
import jnpf.database.util.DataSourceUtil;
import jnpf.database.util.DbTypeUtil;
import jnpf.database.util.JdbcUtil;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.logging.slf4j.Slf4jImpl;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.apache.shardingsphere.driver.api.yaml.YamlShardingSphereDataSourceFactory;
import org.apache.shardingsphere.driver.jdbc.adapter.AbstractDataSourceAdapter;
import org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource;
import org.apache.shardingsphere.infra.datasource.pool.metadata.DataSourcePoolMetaData;
import org.mybatis.spring.annotation.MapperScan;
import org.omg.CORBA.Environment;
import org.springframework.aop.Advisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.util.*;

/**
 * MybatisPlus配置类
 *
 * @author JNPF开发平台组
 * @version V3.1.0
 * @copyright 引迈信息技术有限公司
 * @date 2021/3/16 8:53
 */
@Slf4j
@Configuration
@ComponentScan("jnpf")
@MapperScan(basePackages = {"jnpf.*.mapper", "jnpf.mapper", "com.xxl.job.admin.dao"})
public class MybatisPlusConfig {

    /**
     * 对接数据库的实体层
     */
    static final String ALIASES_PACKAGE = "jnpf.*.entity;com.xxl.job.admin.core.model";

    @Autowired
    private DataSourceUtil dataSourceUtil;
    @Autowired
    private ConfigValueUtil configValueUtil;

    @Primary
    @Bean(name = "dataSourceSystem")
    public DataSource dataSourceOne(DynamicDataSourceProperties properties, DefaultDataSourceCreator dataSourceCreator) throws Exception{
        return dynamicDataSource(properties, dataSourceCreator);
    }

    @Bean(name = "sqlSessionFactorySystem")
    public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceSystem") DataSource dataSource) throws Exception {
        return createSqlSessionFactory(dataSource);
    }

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        try{
            //判断是否多租户
            DbBase dbBase = DbTypeUtil.getDb(dataSourceUtil);
            if (configValueUtil.isMultiTenancy()) {
                DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
                HashMap<String, TableNameHandler> map = new HashMap<>(150) ;
                // null空库保护
                @Cleanup Connection conn = ConnUtil.getConn(dataSourceUtil, null);
                List<String> tableNames = new ArrayList<>() ;
                if(conn != null){
                    PrepareSqlDTO dto = SqlFastUtil.getSysPreDTO(conn, SqlComEnum.TABLES, dataSourceUtil, "");
                    JdbcUtil.queryCustomMods(dto, DbTableFieldModel.class).forEach(dbTableModel-> tableNames.add(dbTableModel.getTable().toLowerCase()));
                }
                //将当前连接库的所有表保存, 在列表中的表才进行切库, 所有表名转小写, 后续比对转小写
                DbBase.dynamicAllTableName = tableNames;
                dynamicTableNameInnerInterceptor.setTableNameHandler(dbBase.getDynamicTableNameHandler());
                interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
            }
            // 新版本分页必须指定数据库，否则分页不生效
            // 不指定会动态生效 多数据源不能指定数据库类型
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor());

            //乐观锁
            interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        }catch (Exception e){
            e.printStackTrace();
        }
        return interceptor;
    }


    protected  DataSource dynamicDataSource(DynamicDataSourceProperties properties, DefaultDataSourceCreator dataSourceCreator) throws Exception {
        // 动态路由数据源（关键）
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setPrimary(properties.getPrimary());
        dataSource.setStrict(properties.getStrict());
        dataSource.setStrategy(properties.getStrategy());
        dataSource.setP6spy(properties.getP6spy());
        dataSource.setSeata(properties.getSeata());
        //若未配置多数据源， 从主配置复制数据库配置填充多数据源
        if(!properties.getDatasource().entrySet().stream().anyMatch(ds->
                ds.getKey().equals(properties.getPrimary()) || ds.getKey().startsWith(properties.getPrimary()+"_") || properties.getPrimary().equals(ds.getValue().getPoolName())
        )){
            // null多租户空库保护
            String url = ConnUtil.getUrl(dataSourceUtil, configValueUtil.isMultiTenancy() ? null : dataSourceUtil.getDbName());
            DataSourceProperty dataSourceProperty = new DataSourceProperty();
            dataSourceProperty.setUsername(dataSourceUtil.getAutoUsername());
            dataSourceProperty.setPassword(dataSourceUtil.getAutoPassword());
            dataSourceProperty.setUrl(url);
            dataSourceProperty.setDriverClassName(DbTypeUtil.getDb(dataSourceUtil).getDriver());
            dataSourceProperty.setDruid(properties.getDruid());
            //不同库设置检查SQL语句,SqlServer, Mysql, Oracle, Postgre已有默认SQL检查器
            if(dataSourceProperty.getDruid().getValidationQuery() == null && (DbTypeUtil.checkKingbase(dataSourceUtil) || DbTypeUtil.checkDM(dataSourceUtil))){
                    dataSourceProperty.getDruid().setValidationQuery("select 1;");
            }
            DataSource ds = dataSourceCreator.createDataSource(dataSourceProperty);
            // oracle参数处理
            if(DbTypeUtil.checkOracle(dataSourceUtil)){
                if(ds instanceof ItemDataSource){
                    ((DruidDataSource)((ItemDataSource) ds).getDataSource()).setConnectProperties(DbOracle.setConnProp("Default", dataSourceUtil.getUserName(), dataSourceUtil.getPassword()));
                }
            }
            dataSource.addDataSource(properties.getPrimary(), ds);
        }

        // 加入ShardingSphere数据源
        if(configValueUtil.isShardingSphereEnabled()){
            File file = new File(MybatisPlusConfig.class.getClassLoader().getResource("sharding-sphere.yml").toURI());
            DataSource SSDataSource = YamlShardingSphereDataSourceFactory.createDataSource(file);
            dataSource.addDataSource("shardingSphere", SSDataSource);
        }
        return dataSource;
    }


    @Bean
    public Advisor myDynamicDatasourceGeneratorAdvisor(DsProcessor dsProcessor) {
        DynamicGeneratorInterceptor interceptor = new DynamicGeneratorInterceptor(true, dsProcessor);
        DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor, DS.class);
        return advisor;
    }

    protected DataSource druidDataSource() throws Exception{
        DbBase dbBase = DbTypeUtil.getDb(dataSourceUtil);
        String userName = dataSourceUtil.getUserName();
        String password = dataSourceUtil.getPassword();
        String driver = dbBase.getDriver();
        String url = "";

        if (configValueUtil.isMultiTenancy()) {
            url = ConnUtil.getUrl(dataSourceUtil, null);
        }else {
            url = ConnUtil.getUrl(dataSourceUtil);
        }

        DruidDataSource dataSource = new DruidDataSource();
        if(dbBase.getClass() == DbOracle.class){
            // Oracle特殊创建数据源方式
//            String logonUer = "Default";
            String logonUer = "SYSDBA";
//            String logonUer = "SYSOPER";
            Properties properties = DbOracle.setConnProp(logonUer, userName, password);
            dataSource.setConnectProperties(properties);
        }else {
            dataSource.setUsername(userName);
            dataSource.setPassword(password);
        }
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        return dataSource;
    }

    public Resource[] resolveMapperLocations() {
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        List<String> mapperLocations = new ArrayList<>();
        mapperLocations.add("classpath*:mapper/*/*.xml");
        mapperLocations.add("classpath*:mapper/*/*/*.xml");
        mapperLocations.add("classpath*:mybatis-mapper/*.xml");
        List<Resource> resources = new ArrayList<Resource>();
        for (String mapperLocation : mapperLocations) {
            try {
                System.out.println(mapperLocation+"记载xml");
                Resource[] mappers = resourceResolver.getResources(mapperLocation);
                System.out.println(mappers+"记载XXML");
                resources.addAll(Arrays.asList(mappers));
            } catch (IOException e) {
                // ignore
            }
        }
        return resources.toArray(new Resource[0]);
    }

    public SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        //全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        //配置填充器
        globalConfig.setMetaObjectHandler(new MybatisPlusMetaObjectHandler());
        bean.setGlobalConfig(globalConfig);

        bean.setVfs(SpringBootVFS.class);
        bean.setTypeAliasesPackage(ALIASES_PACKAGE);
        bean.setMapperLocations(resolveMapperLocations());
        bean.setConfiguration(configuration(dataSource));
        bean.setPlugins(new Interceptor[]{pageHelper(), new MyMasterSlaveAutoRoutingPlugin(dataSource)});
        return bean.getObject();
    }


    public PageInterceptor pageHelper() {
        PageInterceptor pageHelper = new PageInterceptor();
        // 配置PageHelper参数
        Properties properties = new Properties();
        properties.setProperty("dialectAlias", "kingbase8=com.github.pagehelper.dialect.helper.MySqlDialect");
        properties.setProperty("autoRuntimeDialect", "true");
        properties.setProperty("offsetAsPageNum", "false");
        properties.setProperty("rowBoundsWithCount", "false");
        properties.setProperty("pageSizeZero", "true");
        properties.setProperty("reasonable", "false");
        properties.setProperty("supportMethodsArguments", "false");
        properties.setProperty("returnPageInfo", "none");
        pageHelper.setProperties(properties);
        return pageHelper;
    }

    public MybatisConfiguration configuration(DataSource dataSource){
        MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
        mybatisConfiguration.setMapUnderscoreToCamelCase(false);
        mybatisConfiguration.setCacheEnabled(false);
        mybatisConfiguration.addInterceptor(mybatisPlusInterceptor());
        mybatisConfiguration.setLogImpl(Slf4jImpl.class);
        mybatisConfiguration.setJdbcTypeForNull(JdbcType.NULL);
        return mybatisConfiguration;
    }
    @Bean
    public IKeyGenerator keyGenerator() {
        return new H2KeyGenerator();
    }

    @Bean
    public ISqlInjector sqlInjector() {
        return (builderAssistant, mapperClass) -> {

        };
    }

    /**
     * 数据权限插件
     *
     * @return DataScopeInterceptor
     */
//    @Bean
//    @ConditionalOnMissingBean
//    public DataScopeInterceptor dataScopeInterceptor(DataSource dataSource) {
//        return new DataScopeInterceptor(dataSource);
//    }


}
